package com.sunyard.redission.demos.web;


import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.output.TeeOutputStream;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @author zyl
 * @description
 * @since 2025/7/16
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class ExcelExportAspect {

    @Resource
    private final RedissonClient redissonClient;
    @Resource
    protected HttpServletRequest request;
    @Resource
    protected HttpServletResponse response;
    private static final String EXPORT_DIR = "D:/excel_exports";
    @Around("@annotation(excelExport)")
    public Object aroundExport(ProceedingJoinPoint joinPoint, ExcelExport excelExport) throws Throwable {
        // 创建字节数组输出流，用于缓存 Excel 数据
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 根据接口地址和入参生成唯一key
        String exportKey = generateExportKey(request.getRequestURI(), joinPoint);
        // 获取分布式锁
        RLock lock = redissonClient.getLock("export:" + exportKey);
        try {
            // 获取当前请求的 HttpServletResponse
            HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
            // 包装原始 response，使其所有输出都先写入 baos
            HttpServletResponse wrappedResponse = getResponse(response, baos);
            // 替换当前请求的 response，确保 Controller 使用的是我们包装的 response
            RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(
                    ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(),
                    wrappedResponse
            ));
            log.info("尝试获取锁...");
            if (lock.tryLock(excelExport.lockWaitTime(), excelExport.lockLeaseTime(), TimeUnit.SECONDS)) {
                log.info("获取锁成功，开始执行导出");
                if (false) {
                    FileUtils.writeAttachment(response, "stoInventoryArcPlan.xlsx", Files.newInputStream(new File("D:\\excel_exports\\22.xlsx").toPath()));
                    return null;
                }
                // 5. 执行原方法（生成Excel）
                joinPoint.proceed();
                byte[] excelData = baos.toByteArray();
                // 9. 异步上传OSS
                asyncUpload(excelData);
            }
        } finally {
            // 恢复原始 response
            RequestContextHolder.setRequestAttributes(
                    new ServletRequestAttributes(
                            ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(),
                            response
                    )
            );
            baos.close();
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.info("释放锁");
            }
        }
        return null;
    }

    /**
     * 异步上传oss
     * @param excelData
     */
    private static void asyncUpload(byte[] excelData) {
        log.info("开始上传");
        try {
            // 创建存储目录（如果不存在）
            File saveDir = new File(EXPORT_DIR);
            if (!saveDir.exists()) {
                saveDir.mkdirs();
            }

            // 生成带时间戳的文件名
            String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
            String fileName = "export_" + timestamp + ".xlsx";
            File localFile = new File(saveDir, fileName);

            // 写入本地文件
            Files.write(localFile.toPath(), excelData);

            log.info("Excel文件已成功缓存到本地: " + localFile.getAbsolutePath());
        } catch (Exception e) {
            System.err.println("本地缓存Excel失败: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 包装原始 response，使其所有输出都先写入 baos
     * @param response
     * @param baos
     * @return
     */
    private HttpServletResponse getResponse(HttpServletResponse response, ByteArrayOutputStream baos) {
        HttpServletResponse wrappedResponse = new HttpServletResponseWrapper(response) {
            @Override
            public ServletOutputStream getOutputStream() throws IOException {
                return new ServletOutputStream() {
                    @Override
                    public void write(int b) throws IOException {
                        baos.write(b);  // 先写入缓存
                        response.getOutputStream().write(b);  // 再写入真实 response
                    }

                    @Override
                    public boolean isReady() {
                        try {
                            return response.getOutputStream().isReady();
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    @Override
                    public void setWriteListener(WriteListener writeListener) {
                        try {
                            response.getOutputStream().setWriteListener(writeListener);
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    @Override
                    public void flush() throws IOException {
                        response.getOutputStream().flush();
                    }

                    @Override
                    public void close() throws IOException {
                        response.getOutputStream().close();
                    }
                };
            }

            @Override
            public PrintWriter getWriter() throws IOException {
                return new PrintWriter(getOutputStream());
            }
        };
        return wrappedResponse;
    }

    /**
     * 生成导出缓存key
     * @param serviceName 接口地址
     * @param joinPoint
     * @return
     */
    private String generateExportKey(String serviceName, ProceedingJoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        Object params = args.length > 0 ? args[0] : null;
        String paramsJson = JSON.toJSONString(params);
        String hash = DigestUtils.md5Hex(paramsJson);
        return serviceName + ":" + hash;
    }
}
